<template>
	<SizeObserver
			class="bear-TableTree"
			@bear-resize="$_bear_resizeFn">
		<div class="bear-head"
			 ref="head">
			<table>
				<colgroup>
					<col v-for="(width,index) in columnWidths"
						 :width="width"
						 :key="index">
					<col v-if="scrollWidth"
						 :width="scrollWidth">
				</colgroup>
				<thead>
					<tr>
						<th v-for="(column,index) in columns"
							:key="index">
							{{column.title}}
						</th>
						<th v-if="scrollWidth"
							style="padding:0">
						</th>
					</tr>
				</thead>
			</table>
		</div>
		<div class="bear-body"
			 ref="body"
			 @scroll.passive="$_bear_scrollFn">
			<table>
				<colgroup>
					<col v-for="(width,index) in columnWidths"
						 :width="width"
						 :key="index">
				</colgroup>
				<tbody>
					<tr v-for="(row,index) in list"
						:key="index">
						<td v-for="(cell,index) in row"
							:key="index"
							:rowspan="cell.count"
							:class="cell.class"
							@click="$_bear_clickCell(cell,$event)">
							<InputPowerful
									class="bear-edit"
									v-if="cell.column.edit"
									ref="input"
									v-model="cell.title"
									v-bind="cell.editProps"
									v-on="cell.on"/>
							<span v-else
								  class="bear-show"
								  :style="cell.showStyle">{{cell.title|show(cell.column.formatter)}}</span>
							<div class="bear-operation"
								 v-if="cell.column.operation"
								 @click.stop>
								<slot name="add"
									  :cell="cell">
									<Icon @click="$emit('add-cell',cell)"
										  type="ios-add-circle-outline"/>
								</slot>
								<slot name="remove"
									  :cell="cell">
									<Icon @click="$emit('remove-cell',cell)"
										  type="ios-trash-outline"/>
								</slot>
							</div>
						</td>
					</tr>
				</tbody>
			</table>
		</div>
	</SizeObserver>
</template>

<script>
	/* global utils */
	/**
	 * @author 唐国雄
	 * 树表
	 * 属性----------------------
	 * :columns			列属性
	 * :extend-key		扩展信息
	 * :data			展示数据
	 * :select-cell		是否可选择单元格
	 * :select-line		是否可选择行
	 * 插槽----------------------
	 * #add				单元格新增按钮
	 * #remove			单元格删除按钮
	 * 事件----------------------
	 * @add-cell		点击单元格新增按钮
	 * @remove-cell		点击单元格删除按钮
	 * @click-cell		点击单元格
	 * 方法----------------------
	 * getOperateCell()			当前被操作的单元格
	 * getActiveCell()			当前选中的单元格
	 * getActiveLine()			被选中行包含的单元格
	 * getRowHead(cell)			获取指定单元格所在行,的最顶层单元格
	 * getList(cell)			获取指定单元格所在行的数据对象列表,没有指定单元格则表示全部
	 * addChild(cell,text,force)		新增指定单元格的子节点
	 * insertSibling(cell,text,force)	在指定单元格后插入兄弟节点
	 * deleteSelf(cell)					删除指定单元格及其子节点
	 * deleteRow(cell)				删除指定单元格所在行
	 * modifySelf(cell,text)		修改当前单元格内容
	 * modifySingleRow(cell,obj)	修改指定单元格所在单行
	 *
	 * @typedef TableTree
	 * @event TableTree#add-cell
	 * @type {NodeCell} 执行操作的单元格
	 * @event TableTree#remove-cell
	 * @type {NodeCell} 执行操作的单元格
	 * @event TableTree#click-cell
	 * @type {NodeCell} 被点击的单元格
	 */
	import SizeObserver from './SizeObserver';
	import InputPowerful from './InputPowerful';
	export default{
		name:'TableTree',
		components:{
			SizeObserver,
			InputPowerful
		},
		filters:{
			show(val,formatter){
				if(formatter){
					return formatter(val);
				}
				return val;
			}
		},
		props:{
			/**
			 * 单元格节点
			 * @typedef NodeCell
			 * @property {String} title 内容
			 * @property {NodeCell[]} children 子节点
			 * @property {NodeCell} parent 父节点
			 * @property {Number} count 末级节点数量
			 * @property {Column} column 列属性
			 * @property {Column} on 生成的监听列表(代理了Column.on), 发射出当前单元格
			 * @property {Object} [rawObj] 原始对象(只在末级节点存在)
			 * @property {Object} class 类名状态
			 * @property {Object} [showStyle] 当不是edit时,应用的样式
			 * @property {Object} [editProps] 当是edit时,应用的属性
			 */
			/**
			 * 列属性
			 * @typedef Column
			 * @property {String} title 列标题
			 * @property {String} key 属性名
			 * @property {String|Number} width 列宽,数值表示固定宽度px,百分比表示占总宽的百分比,fr表示剩余宽度的权重
			 * @property {String} align 对齐方式,'left','center','right'
			 * @property {String} whiteSpace 空白字符处理规则,'normal','pre','nowrap','pre-wrap','pre-line'
			 * @property {Boolean} merge 是否合并
			 * @property {String} edit 可编辑类型,'text','textarea'
			 * @property {Boolean} operation 是否可增删
			 * @property {*} default 默认值,参考utils.defaultTemplate
			 * @property {Formatter} formatter 格式化器
			 * @property {Validator} validator 验证器(只有设置了edit以后才能设置)
			 * @property {Boolean} required 是否必填(只有设置了edit以后才能设置)
			 * @property {Object} on 编辑框上的监听列表(只有设置了edit以后才能设置)
			 */
			/**
			 * 格式化器,只在校验成功后调用(可能为空)
			 * @function Formatter
			 * @param {String} val 输入框当前值
			 * @return {String} 格式化以后的值
			 */
			/**
			 * 验证器,只在值不为空时调用
			 * @function Validator
			 * @param {NodeCell} cell 当前单元格
			 * @param {String} val 输入框当前值
			 * @return {String|Boolean} 校验的失败的错误信息,或者校验的状态
			 */
			/**
			 * 定义列
			 * @type {Column[]}
			 */
			columns:Array,
			// 扩展字段,参考utils.defaultTemplate
			extendKey:Object,
			/**
			 * 展示数据
			 * @type {Object[]}
			 */
			data:Array,
			// 是否可以选中单元格
			selectCell:Boolean,
			// 是否可以选中行
			selectLine:Boolean
		},
		data(){
			return {
				width:0,
				// 垂直滚动条宽度
				scrollWidth:0,
				// 列宽数值
				columnWidths:[],
				// 宽度列表生成器
				calcColumnWidthFn:utils.getCalcColumnWidthFn(this.columns.map(function(ele){
					return ele.width;
				})),
				// 滚动事件频率控制
				ticking:false,
				/**
				 * 树表
				 * @typedef CellRoot
				 * @property {NodeCell[]} children 子节点
				 * @property {Number} count 末级节点数量
				 * @property {Boolean} isRoot 是否是根节点,固定为true
				 */
				root:{},
				/**
				 * 当前被操作的单元格
				 * @type {NodeCell}
				 */
				operateCell:null,
				/**
				 * 当前选中的单元格
				 * @type {NodeCell}
				 */
				activeCell:null,
				/**
				 * 被选中行包含的单元格
				 * @type {NodeCell[]}
				 */
				activeLine:[]
			};
		},
		computed:{
			list(){
				const list=[];
				const tree=this.root.children;
				let count=0;
				if(tree.length){
					count=tree[0].parent.count;
				}
				for(let i=0;i<count;++i){
					list[i]=[];
				}
				buildRefList(tree,list,0,this);
				return list;
			}
		},
		watch:{
			data(){
				convertData(this);
			},
			columns:{
				handler(){
					this.calcColumnWidthFn=utils.getCalcColumnWidthFn(this.columns.map(function(ele){
						return ele.width;
					}));
					convertData(this);
				},
				deep:true
			},
			selectCell(val){
				if(!val){
					this.activeCell=null;
				}
			},
			selectLine(val){
				if(!val){
					this.activeLine=[];
				}
			}
		},
		created(){
			convertData(this);
			// 监听宽度改变
			this.$watch(function(){
				return this.width-this.scrollWidth;
			},function(val){
				this.columnWidths=this.calcColumnWidthFn(val);
				this.$nextTick(function(){
					const inputs=this.$refs.input||[];
					for(let i=inputs.length-1;i>=0;--i){
						inputs[i].autoHeight();
						inputs[i].updateMaxWidth();
					}
				});
			});
		},
		mounted(){
			calcScrollWidth(this);
		},
		updated(){
			calcScrollWidth(this);
		},
		methods:{
			$_bear_resizeFn(data){
				this.width=data.width;
				calcScrollWidth(this);
			},
			$_bear_scrollFn(){
				if(!this.ticking){
					this.ticking=true;
					const ref=this.$refs;
					const scrollLeft=ref.body.scrollLeft;
					requestAnimationFrame(() => {
						ref.head.scrollLeft=scrollLeft;
						this.ticking=false;
					});
				}
			},
			$_bear_clickCell(cell,event){
				this.$emit('click-cell',cell);
				if(this.selectCell){
					this.activeCell=this.activeCell===cell?null:cell;
				}
				if(this.selectLine){
					const list=[];
					pushAllChild(this.getRowHead(cell),list);
					this.activeLine=list;
				}
				if(cell.column.operation){
					this.operateCell=cell;
				}
				if(cell.column.edit){
					event.currentTarget.querySelector('.bear-InputPowerful').focus();
				}
			},
			getOperateCell(){
				const operateCell=this.operateCell;
				return operateCell&&operateCell.column.operation?operateCell:null;
			},
			getActiveCell(){
				return this.activeCell;
			},
			getActiveLine(){
				return this.activeLine;
			},
			/**
			 * 获取指定单元格所在行,的最顶层单元格
			 * @param {NodeCell} cell 指定单元格
			 * @returns {NodeCell} 最顶层单元格
			 */
			getRowHead(cell){
				let parent=cell.parent;
				while(!parent.isRoot&&parent.children.length===1){
					cell=parent;
					parent=cell.parent;
				}
				return cell;
			},
			/**
			 * 获取指定单元格所在行的数据对象列表,没有指定单元格则表示全部
			 * @param {NodeCell} [cell] 指定单元格
			 * @returns {Object[]} 数据对象列表
			 */
			getList(cell){
				const base={};
				let cellList;
				if(cell){
					cellList=cell.children;
					do{
						const column=cell.column;
						base[column.key]=utils.defaultTemplate(column.default,cell.title);
						cell=cell.parent;
					}while(cell.column);
				}else{
					cellList=this.root.children;
				}
				const dataList=[];
				getListInner(base,cellList,dataList,this);
				return dataList;
			},
			/**
			 * 新增指定单元格的子节点
			 * @param {NodeCell} cell 指定单元格
			 * @param {String} text 子节点内容
			 * @param {Boolean} [force] 是否强制(不需要列属性merge为true)
			 * @return {Boolean} 新增是否成功
			 */
			addChild(cell,text,force){
				cell=cell||this.root;
				const column=cell.column;
				const columns=this.columns;
				let index=columns.indexOf(column);
				const len=columns.length;
				if(index<len-1&&(force||cell.children.length===0||!column||column.merge)){
					let cur=cell;
					let next;
					while(++index<len){
						next={
							title:text,
							children:[],
							parent:cur,
							count:1,
							column:columns[index]
						};
						cur.children.push(next);
						cur=next;
						text='';
					}
					do{
						++cell.count;
						cell=cell.parent;
					}while(cell);
					return true;
				}
				return false;
			},
			/**
			 * 在指定单元格后插入兄弟节点
			 * @param {NodeCell} cell 指定单元格
			 * @param {String} text 兄弟节点内容
			 * @param {Boolean} [force] 是否强制(不需要列属性merge为true)
			 * @return {Boolean} 插入是否成功
			 */
			insertSibling(cell,text,force){
				if(!cell) return false;
				const parent=cell.parent;
				const column=parent.column;
				if(force||parent.children.length===0||!column||column.merge){
					const cur={
						title:text,
						children:[],
						parent:parent,
						count:0,// 注意这里是0,后面addChild会加回来
						column:cell.column
					};
					const list=parent.children;
					list.splice(list.indexOf(cell)+1,0,cur);
					return this.addChild(cur,'',true);
				}
				return false;
			},
			/**
			 * 删除指定单元格及其子节点
			 * @param {NodeCell} cell 指定单元格
			 */
			deleteSelf(cell){
				const index=this.columns.indexOf(cell.column);
				let parent=cell.parent;
				const children=parent.children;
				const count=cell.count;
				children.splice(children.indexOf(cell),1);
				cell=parent;
				do{
					parent.count-=count;
					parent=parent.parent;
				}while(parent);
				if(index!==0&&!children.length){
					this.addChild(cell,'',true);
				}
			},
			/**
			 * 删除指定单元格所在行
			 * @param {NodeCell} cell 指定单元格
			 */
			deleteRow(cell){
				this.deleteSelf(this.getRowHead(cell));
			},
			/**
			 * 修改当前单元格内容
			 * @param {NodeCell} cell 单元格
			 * @param {String} text 内容
			 */
			modifySelf(cell,text){
				cell.title=text;
			},
			/**
			 * 修改指定单元格所在单行
			 * @param {NodeCell} cell 指定单元格
			 * @param {Object} obj 数据对象
			 */
			modifySingleRow(cell,obj){
				cell=this.getRowHead(cell);
				if(cell.count===1){
					do{
						cell.title=obj[cell.column.key];
						cell=cell.children[0];
					}while(cell);
				}
			}
		}
	};
	// ==========================私有方法==============================
	/**
	 * 构建内部使用的树表结构,并绑定到响应属性root上
	 * @param {Object} vm Vue实例对象
	 */
	function convertData(vm){
		vm.activeCell=null;
		vm.operateCell=null;
		vm.activeLine=[];
		const tree=[];
		const data=vm.data;
		const root={
			children:tree,
			count:data.length,
			isRoot:true
		};
		buildTree(tree,data,0,root,vm);
		vm.root=root;
	}
	/**
	 * 根据列的定义和列表结构的源数据构建树表
	 * @param {NodeCell[]} container 目标容器
	 * @param {Object[]} list 数据对象列表
	 * @param {Number} index 列索引
	 * @param {CellRoot|NodeCell} parent 父节点
	 * @param {Object} vm Vue实例对象
	 */
	function buildTree(container,list,index,parent,vm){
		const column=vm.columns[index];
		if(!column){
			// 在末级节点添加原始对象
			parent.rawObj=list[0];
			return;
		}
		const key=column.key;
		if(column.merge){
			for(const ele of list){
				const title=utils.defaultTemplate('',ele[key]);
				let notExist=true;
				for(const innerEle of container){
					if(innerEle.title===title){
						notExist=false;
						innerEle.list.push(ele);
						++innerEle.count;
						break;
					}
				}
				if(notExist){
					container.push({
						title:title,
						children:[],
						parent:parent,
						count:1,
						column:column,
						list:[ele]
					});
				}
			}
		}else{
			for(const ele of list){
				container.push({
					title:utils.defaultTemplate('',ele[key]),
					children:[],
					parent:parent,
					count:1,
					column:column,
					list:[ele]
				});
			}
		}
		for(const ele of container){
			buildTree(ele.children,ele.list,index+1,ele,vm);
			delete ele.list;
		}
	}
	/**
	 * 把树表结构映射到列表结构,方便构建表格
	 * @param {NodeCell[]} tree 树表结构的源数据
	 * @param {NodeCell[][]} list 映射到的列表结构
	 * @param {Number} index 开始索引
	 * @param {Object} vm Vue实例对象
	 */
	function buildRefList(tree,list,index,vm){
		if(!tree) return;
		for(const ele of tree){
			const column=ele.column;
			const edit=column.edit;
			if(column.on){
				const on={};
				const listenKeys=Object.keys(column.on);
				for(let i=0;i<listenKeys.length;++i){
					const listenKey=listenKeys[i];
					on[listenKey]=function(event){
						column.on[listenKey](ele,event);
					};
				}
				ele.on=on;
			}
			ele.class={
				'bear-operateCell':vm.getOperateCell()===ele,
				'bear-activeCell':vm.getActiveCell()===ele,
				'bear-activeLine':vm.getActiveLine().includes(ele),
				'bear-editText':edit==='text',
				'bear-editTextarea':edit==='textarea'
			};
			if(edit){
				ele.editProps={
					required:column.required,
					formatter:column.formatter,
					align:column.align
				};
				switch(edit){
					case 'text':
						ele.editProps.wrap='input';
						break;
					case 'textarea':
						ele.editProps.wrap='TextareaAutoHeight';
				}
				const validator=column.validator;
				if(validator){
					ele.editProps.validator=validator.bind(null,ele);
				}
			}else{
				switch(column.align){
					case 'right':
						ele.showStyle={
							justifyContent:'flex-end',
							textAlign:'right'
						};
						break;
					case 'center':
						ele.showStyle={
							justifyContent:'center',
							textAlign:'center'
						};
						break;
					default:
						ele.showStyle={
							justifyContent:'flex-start',
							textAlign:'left'
						};
				}
				ele.showStyle.whiteSpace=column.whiteSpace;
			}
			list[index].push(ele);
			buildRefList(ele.children,list,index,vm);
			index+=ele.count;
		}
	}
	/**
	 * 把指定单元格及其所有子代单元格放进目标容器
	 * @param {NodeCell} cell 指定单元格
	 * @param {NodeCell[]} container 目标容器
	 */
	function pushAllChild(cell,container){
		container.push(cell);
		for(const ele of cell.children){
			pushAllChild(ele,container);
		}
	}
	/**
	 * 通过单元格列表获取数据对象列表
	 * @param {Object} base 基础信息对象
	 * @param {NodeCell[]} cellList 单元格列表
	 * @param {Object[]} dataList 数据对象列表
	 * @param {Object} vm Vue实例对象
	 */
	function getListInner(base,cellList,dataList,vm){
		for(const cell of cellList){
			const nextBase=Object.assign({},base);
			const column=cell.column;
			nextBase[column.key]=utils.defaultTemplate(column.default,cell.title);
			const children=cell.children;
			if(children.length){
				getListInner(nextBase,children,dataList,vm);
			}else{
				dataList.push(utils.extend(nextBase,utils.defaultTemplate(vm.extendKey,cell.rawObj)));
			}
		}
	}
	/**
	 * 计算滚动条宽度
	 * @param {Object} vm Vue实例对象
	 */
	function calcScrollWidth(vm){
		const body=vm.$refs.body;
		vm.scrollWidth=body.offsetWidth-body.clientWidth;
	}
</script>

<style lang="scss">
	$border-inner-color:#e8eaec;
	$border-outer-color:#dcdee2;
	$head-bg-color:#f8f8f9;
	$head-color:#000;
	$color:#000;
	$active-line:#ccebff;
	$active-cell:#1ba4ff;
	$active-cell-font:#fff;
	$operation-add:#40e02c;
	$operation-remove:red;
	.bear-TableTree{
		font-size:14px;
		color:$color;
		background:#fff;
		position:relative;
		display:flex;
		flex-direction:column;
		&:before,&:after{
			content:'';
			position:absolute;
			height:100%;
			width:1px;
			background:$border-outer-color;
			top:0;
		}
		&:before{
			left:0;
		}
		&:after{
			right:0;
		}
		table{
			border-collapse:separate;
			border-spacing:0;
			table-layout:fixed;
		}
		>.bear-head{
			width:100%;
			overflow:hidden;
			flex-shrink:0;
			>table{
				width:100%;
				>thead{
					background-color:$head-bg-color;
					font-size:14px;
					color:$head-color;
					th{
						border-top:1px solid $border-outer-color;
						font-weight:normal;
					}
				}
			}
		}
		>.bear-body{
			width:100%;
			overflow:visible;
			flex-grow:1;
			>table{
				width:100%;
			}
			&:after{
				content:'';
				position:absolute;
				width:100%;
				height:1px;
				background:$border-outer-color;
				bottom:0;
			}
		}
		th,td{
			position:relative;
			border:1px solid $border-inner-color;
			border-top:0;
			border-left:0;
			height:34px;
			overflow:hidden;
			word-wrap:break-word;
			padding:0 10px;
			&.bear-activeLine{
				background:$active-line;
			}
			&.bear-activeCell{
				background:$active-cell;
				color:$active-cell-font;
				>.bear-edit{
					color:$active-cell-font;
				}
			}
			&.bear-operateCell{
				overflow:visible;
				>.bear-operation{
					display:flex;
				}
			}
			&.bear-editText,&.bear-editTextarea{
				cursor:text;
			}
			&.bear-editText{
				>.bear-edit{
					height:100%;
				}
			}
			&.bear-editTextarea{
				vertical-align:top;
			}
			>.bear-operation{
				color:#000;
				position:absolute;
				display:none;
				left:-18px;
				top:0;
				height:100%;
				flex-direction:column;
				align-items:center;
				justify-content:center;
				>*{
					cursor:pointer;
					font-size:18px;
					&:first-child{
						color:$operation-add;
					}
					&:last-child{
						color:$operation-remove;
					}
				}
			}
		}
		.bear-edit{
			width:100%;
			border:0;
			outline:0;
			font-size:14px;
			color:$color;
			padding-left:1px;
			padding-right:1px;
			background:transparent;
		}
		.bear-show{
			display:flex;
			align-items:center;
		}
	}
</style>
